﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CNWebBase
{
    public static class Cache
    {
        private static object StaticLock = new object();
        private static Dictionary<long, User> AllUsers;
        private static Dictionary<long, Organization> AllOrganizations;
        private static Dictionary<long, Role> AllRoles;
        private static Dictionary<long, Position> AllPositions;
        private static Dictionary<Tuple<string, long>, string> AllLabelL10n;

        internal delegate void CacheInitializedEventHandler();
        internal static event CacheInitializedEventHandler CacheInitialized;

        public static void InitCache()
        {
            lock (StaticLock)
            {
                using (CNWebBaseContainer cCache = new CNWebBaseContainer())
                {
                    AllUsers = cCache.User
                        .Include("Language.LabelL10n")
                        .Include("Position")
                        .Include("Role")
                        .Include("Organization")
                        .ToDictionary(user => user.ID);

                    AllOrganizations = cCache.Organization
                        .Include("Parent")
                        .Include("Children")
                        .Include("Position.Role")
                        .ToDictionary(org => org.ID);

                    AllPositions = AllOrganizations.Values.SelectMany(org => org.Position).ToDictionary(pos => pos.ID);

                    AllRoles = cCache.Role
                        .Include("FeatureEntry.Children")
                        .Include("FeatureEntry.FeatureType")
                        .Include("FeatureEntry.Parent")
                        .Include("FeatureEntry.FeatureEntryL10n")
                        .Include("RoleType")
                        .Include("AddableResourceEntry")
                        .Include("AlterableResourceEntry")
                        .Include("DeleteableResourceEntry")
                        .Include("SelectableResourceEntry")
                        .Include("WorkFlowTemplateNode.WorkFlowTemplate")
                        .Include("WorkFlowTemplateNode.LastStepDirector")
                        .Include("WorkFlowTemplateNode.NextStepDirector")
                        .ToDictionary(role => role.ID);

                    foreach (var user in AllUsers.Values)
                    {
                        ReCalculateUser(user);
                    }

                    AllLabelL10n = cCache.LabelL10n.ToDictionary(l_key => new Tuple<string, long>(l_key.Code, l_key.LanguageID), l_value => l_value.Text);
                }
                GC.Collect();
                if (CacheInitialized != null) { CacheInitialized(); }
            }
        }

        private static void ReCalculateUser(User BasicUser)
        {
            var vNewRoles = AllRoles.Values.Where(newrole => BasicUser.Role.Select(role => role.ID).Contains(newrole.ID));
            BasicUser.Role.Clear();
            foreach (var role in vNewRoles) { BasicUser.Role.Add(role); }

            var vNewOrgs = AllOrganizations.Values.Where(neworg => BasicUser.Organization.Select(org => org.ID).Contains(neworg.ID));
            BasicUser.Organization.Clear();
            foreach (var org in vNewOrgs) { BasicUser.Organization.Add(org); }

            var vNewPositions = AllPositions.Values.Where(newpos => BasicUser.Position.Select(pos => pos.ID).Contains(newpos.ID));
            BasicUser.Position.Clear();
            foreach (var pos in vNewPositions) { BasicUser.Position.Add(pos); }
        }
        public static void RefreshCache()
        {
            InitCache();
        }

        internal static string[] GetRoleValidateMethodNames()
        {
            return AllRoles.Values.Where(role => role.ValidateMethodName != null).Select(role => role.ValidateMethodName).ToArray();
        }

        /// <summary>
        /// Get user from cache with specified user name
        /// </summary>
        /// <param name="UserName">user name</param>
        /// <returns>required user, will be defalutl value of User(null) if no user match</returns>
        public static User GetUser(string UserName)
        {
            return AllUsers.Values.SingleOrDefault(user => user.Name == UserName);
        }
        public static User GetUser(long UserID)
        {
            return AllUsers.Values.SingleOrDefault(user => user.ID == UserID);
        }
        public static void UpdateUserBasic(User NewUser)
        {
            lock (StaticLock)
            {
                User uCached = GetUser(NewUser.ID);
                if (uCached != default(User))
                {
                    uCached.LanguageID = NewUser.LanguageID;
                    uCached.LastLoginTime = NewUser.LastLoginTime;
                    uCached.Name = NewUser.Name;
                    uCached.NickName = NewUser.NickName;
                    uCached.PasswordDigit = NewUser.PasswordDigit;
                    uCached.UserCache = NewUser.UserCache;
                }
                else
                {
                    throw new InvalidProgramException("User not cached.");
                }
            }
        }
        public static void ReloadUser(long UserID)
        {
            lock (StaticLock)
            {
                using (CNWebBaseContainer cCache = new CNWebBaseContainer())
                {
                    User FreshUser= cCache.User
                        .Include("Language.LabelL10n")
                        .Include("Position")
                        .Include("Role")
                        .Include("Organization")
                        .FirstOrDefault(user => user.ID == UserID);
                    if (FreshUser != default(User))
                    {
                        if (AllUsers.Keys.Contains(UserID))
                        {
                            AllUsers.Remove(UserID);
                        }
                        ReCalculateUser(FreshUser);
                        AllUsers.Add(UserID, FreshUser);
                    }
                    else
                    {
                        throw new InvalidProgramException("User not in database.");
                    }
                }
            }
        }
    }
}
